package org.korifey.ffbdd.samples.logic;

import java.util.Deque;
import java.util.LinkedList;

import org.korifey.ffbdd.samples.logic.Token.TYPE;

public class ExpressionParser {

	int count = 0;
	String expr;
	Deque<Token> pushback = new LinkedList<Token>();

	public ExpressionParser(String expr) {
		this.expr = expr;
	}

	
	Token next() {
		if (pushback.size() > 0)
			return pushback.pollFirst();

		while (count < expr.length() && Character.isWhitespace(expr.charAt(count))) count++;
		if (count >= expr.length())
			return null;
		
		int idx = count;
		char c = expr.charAt(count);	

		if (Character.isJavaIdentifierStart(c)) {
			while (count < expr.length()
					&& Character.isJavaIdentifierPart(expr.charAt(count)))
				count++;
			return new Token(Token.TYPE.VARIABLE, expr.substring(idx, count),
					idx);

		} else if (Character.isDigit(c)) {
			while (count < expr.length()
					&& Character.isDigit(expr.charAt(count)))
				count++;
			return new Token(Token.TYPE.CONST, expr.substring(idx, count), idx);

		} else if (c == '(') {
			return new Token(Token.TYPE.LPAR, expr.charAt(count++) + "", idx);

		} else if (c == ')') {
			return new Token(Token.TYPE.RPAR, expr.charAt(count++) + "", idx);

		} else {
			if (count >= expr.length())
				throw new ParseException(
						"Error while parsing operation on pos: "
								+ idx
								+ ","
								+ " 2 letters expected while only 1 is available before expression's end");
			count +=2;
			return new Token(Token.TYPE.OP, expr.substring(idx, count), idx);
		}

	}

	void pushback(Token t) {
		pushback.addFirst(t);
	}

	/**
	 * EXPR = BINOP 
	 * BINOP = ELEM [op BINOP] 
	 * ELEM = var | const | '!!' ELEM | '(' EXPR ')'
	 * 
	 * @return
	 */

	public Expression parse() {
		return parseBinop();
	}

	private Expression parseBinop() {
		Expression elem = parseElem();
		Token t = next();
		if (t == null) return elem;
		if ((t.getType() != TYPE.OP) || (t.getLexem().equals(Operation.TYPE.NEG.getLexem()))) {
			pushback(t);
			return elem;
		}
			//throw new ParseException("Parse exception: Binary operation expected at pos:"+t.getIdx()+" but '"+t.getLexem()+"' received" );
		
		Expression elem2 = parseBinop();
		return new Operation(elem, elem2, Operation.TYPE.getType(t.getLexem()));
	}
	
	private Expression parseElem() {
		Expression ex;
		Token t = next();
		if (t == null) throw new ParseException("Parse exception: Token expected but end of string received");
		
		switch (t.getType()) {
		case CONST: 
			return new Const(t.getLexem());
		case VARIABLE:
			return new Variable(t.getLexem());
		case OP:
			if (! Operation.TYPE.NEG.equals(Operation.TYPE.getType(t.getLexem()))) {
				throw new ParseException("Parsing error: unary operation "+t.getLexem()+" at pos "+t.getIdx());
			}
			ex = parseElem();
			return new Operation(ex, null, Operation.TYPE.NEG);
		case LPAR:
			ex = parseBinop();
			t = next();
			int pos = (t == null) ? expr.length() : t.getIdx();
			if (t == null || !(t.getType() == TYPE.RPAR)) throw new ParseException("Parsing error: ')' expected at pos "+pos);
			return ex;
		default:
			throw new ParseException("Unexpected token type "+t.getType()+" at pos "+t.idx);
		}
	}
}
